use actix_web::{
    body::MessageBody,
    dev::{self, ServiceRequest, ServiceResponse},
    http::header::HeaderMap,
    middleware::Next,
    web, Error,
};
use chrono::Local;
use log::{error, info};
use serde_json::json;
use std::time::Instant;

pub async fn http_logger(
    mut req: ServiceRequest,
    next: Next<impl MessageBody + 'static>,
) -> Result<ServiceResponse<impl MessageBody>, Error> {
    let method = req.method().clone().as_str().to_string();
    let path = req.path().to_string();
    let query_string = req.query_string().to_string();
    let headers = req.headers().clone();
    let headers_json = headers_to_json(&headers);
    let client_ip = extract_client_ip(&req, &headers);
    let start_instant = Instant::now();
    let start_time_str = Local::now().format("%Y-%m-%d %H:%M:%S%.3f").to_string();

    let is_file_transfer = path.contains("/upload") || path.contains("/download");
    let req_body_str = if is_file_transfer {
        String::from("文件传输")
    } else {
        extract_request_body(&mut req).await?
    };

    let res = next.call(req).await?;
    let (req, res) = res.into_parts();
    let (res, body) = res.into_parts();
    let body_bytes = match body.try_into_bytes() {
        Ok(bytes) => bytes,
        _ => return Err(actix_web::error::ErrorImATeapot("teapot")),
    };

    let res_headers = res.headers().clone();

    let is_file_transfer = res_headers
        .get("Content-Type")
        .map(|value| {
            let content_type = value.to_str().unwrap_or("");
            content_type.starts_with("image/") || content_type.starts_with("text/")
        })
        .unwrap_or(false);

    let res_body_str = if is_file_transfer {
        String::from("文件传输")
    } else {
        String::from_utf8_lossy(&body_bytes).to_string()
    };

    let log_data = LogData {
        path: &path,
        method: &method,
        query_string: &query_string,
        req_body_str: &req_body_str,
        headers_json: &headers_json,
        start_time_str: &start_time_str,
        client_ip: &client_ip,
        status: res.status(),
        body_str: &res_body_str,
        elapsed_millis: start_instant.elapsed().as_millis(),
    };

    log_request_response(log_data);
    let res = res.set_body(body_bytes);
    Ok(ServiceResponse::new(req, res))
}

async fn extract_request_body(req: &mut ServiceRequest) -> Result<String, Error> {
    match req.extract::<web::Bytes>().await {
        Ok(body) => {
            let body_str = String::from_utf8_lossy(&body)
                .replace("\r\n", "")
                .replace("\n", "");
            req.set_payload(bytes_to_payload(body));
            Ok(body_str.to_string())
        }
        Err(e) => {
            error!("Failed to extract body: {:?}", e);
            Err(actix_web::error::ErrorBadRequest("Failed to extract body"))
        }
    }
}

fn extract_client_ip(req: &ServiceRequest, headers: &HeaderMap) -> String {
    headers
        .get("X-Forwarded-For")
        .and_then(|header| header.to_str().ok())
        .map(|header| header.split(',').next().unwrap_or("").trim().to_string())
        .or_else(|| {
            headers
                .get("X-Real-IP")
                .and_then(|header| header.to_str().ok())
                .map(|header| header.to_string())
        })
        .unwrap_or_else(|| {
            req.peer_addr()
                .map(|addr| addr.ip().to_string())
                .unwrap_or_default()
        })
}
struct LogData<'a> {
    path: &'a str,
    method: &'a str,
    query_string: &'a str,
    req_body_str: &'a str,
    headers_json: &'a serde_json::Value,
    start_time_str: &'a str,
    client_ip: &'a str,
    status: actix_web::http::StatusCode,
    body_str: &'a str,
    elapsed_millis: u128,
}

fn log_request_response(log_data: LogData) {
    let mut log_message = format!(
        "\n\
        ================================  网络请求响应日志  ==================================\n\
        请求路径: {}\n\
        请求方法: {}\n",
        log_data.path, log_data.method,
    );

    if !log_data.query_string.is_empty() {
        log_message.push_str(&format!("请求参数: {}\n", log_data.query_string));
    }

    if !log_data.req_body_str.is_empty() {
        log_message.push_str(&format!("请求数据: {}\n", log_data.req_body_str));
    }

    log_message.push_str(&format!(
        "请求头部: {}\n\
        请求时间: {}\n\
        请求地址: {}\n\
        响应状态: {}\n\
        响应数据: {}\n\
        响应时间: {}\n\
        执行耗时: {} 毫秒\n\
        ======================================================================================",
        log_data.headers_json,
        log_data.start_time_str,
        log_data.client_ip,
        log_data.status,
        log_data.body_str,
        Local::now().format("%Y-%m-%d %H:%M:%S%.3f"),
        log_data.elapsed_millis,
    ));
    info!("{}", log_message);
}

fn bytes_to_payload(buf: web::Bytes) -> dev::Payload {
    dev::Payload::from(buf)
}

fn headers_to_json(headers: &HeaderMap) -> serde_json::Value {
    let mut map = serde_json::Map::new();
    for (key, value) in headers.iter() {
        let values: Vec<String> = value
            .to_str()
            .unwrap_or("")
            .split(',')
            .map(|s| s.trim().to_string())
            .collect();
        map.insert(key.to_string(), json!(values));
    }
    json!(map)
}
